home *** CD-ROM | disk | FTP | other *** search
/ HAKERIS 11 / HAKERIS 11.ISO / linux / system / LinuxConsole 0.4 / linuxconsole0.4install-en.iso / guile0.4.lcm / share / guile / 1.6.0 / srfi / srfi-19.scm < prev    next >
Encoding:
Text File  |  2004-01-06  |  59.2 KB  |  1,562 lines

  1. ;;; srfi-19.scm --- Time/Date Library
  2.  
  3. ;;     Copyright (C) 2001, 2002 Free Software Foundation, Inc.
  4. ;;
  5. ;; This program is free software; you can redistribute it and/or
  6. ;; modify it under the terms of the GNU General Public License as
  7. ;; published by the Free Software Foundation; either version 2, or
  8. ;; (at your option) any later version.
  9. ;;
  10. ;; This program is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. ;; General Public License for more details.
  14. ;;
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with this software; see the file COPYING.  If not, write to
  17. ;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  18. ;; Boston, MA 02111-1307 USA
  19. ;;
  20. ;; As a special exception, the Free Software Foundation gives permission
  21. ;; for additional uses of the text contained in its release of GUILE.
  22. ;;
  23. ;; The exception is that, if you link the GUILE library with other files
  24. ;; to produce an executable, this does not by itself cause the
  25. ;; resulting executable to be covered by the GNU General Public License.
  26. ;; Your use of that executable is in no way restricted on account of
  27. ;; linking the GUILE library code into it.
  28. ;;
  29. ;; This exception does not however invalidate any other reasons why
  30. ;; the executable file might be covered by the GNU General Public License.
  31. ;;
  32. ;; This exception applies only to the code released by the
  33. ;; Free Software Foundation under the name GUILE.  If you copy
  34. ;; code from other Free Software Foundation releases into a copy of
  35. ;; GUILE, as the General Public License permits, the exception does
  36. ;; not apply to the code that you add in this way.  To avoid misleading
  37. ;; anyone as to the status of such modified files, you must delete
  38. ;; this exception notice from them.
  39. ;;
  40. ;; If you write modifications of your own for GUILE, it is your choice
  41. ;; whether to permit this exception to apply to your modifications.
  42. ;; If you do not wish that, delete this exception notice.
  43.  
  44. ;;; Author: Rob Browning <rlb@cs.utexas.edu>
  45. ;;;         Originally from SRFI reference implementation by Will Fitzgerald.
  46.  
  47. ;;; Commentary:
  48.  
  49. ;; This module is fully documented in the Guile Reference Manual.
  50.  
  51. ;;; Code:
  52.  
  53. ;; FIXME: I haven't checked a decent amount of this code for potential
  54. ;; performance improvements, but I suspect that there may be some
  55. ;; substantial ones to be realized, esp. in the later "parsing" half
  56. ;; of the file, by rewriting the code with use of more Guile native
  57. ;; functions that do more work in a "chunk".
  58. ;;
  59. ;; FIXME: mkoeppe: Time zones are treated a little simplistic in
  60. ;; SRFI-19; they are only a numeric offset.  Thus, printing time zones
  61. ;; (PRIV:LOCALE-PRINT-TIME-ZONE) can't be implemented sensibly.  The
  62. ;; functions taking an optional TZ-OFFSET should be extended to take a
  63. ;; symbolic time-zone (like "CET"); this string should be stored in
  64. ;; the DATE structure.
  65.  
  66. (define-module (srfi srfi-19)
  67.   :use-module (srfi srfi-6)
  68.   :use-module (srfi srfi-8)
  69.   :use-module (srfi srfi-9))
  70.  
  71. (begin-deprecated
  72.  ;; Prevent `export' from re-exporting core bindings.  This behaviour
  73.  ;; of `export' is deprecated and will disappear in one of the next
  74.  ;; releases.
  75.  (define current-time #f))
  76.  
  77. (export ;; Constants
  78.            time-duration
  79.            time-monotonic
  80.            time-process
  81.            time-tai
  82.            time-thread
  83.            time-utc
  84.            ;; Current time and clock resolution
  85.            current-date
  86.            current-julian-day
  87.            current-modified-julian-day
  88.            current-time
  89.            time-resolution
  90.            ;; Time object and accessors
  91.            make-time
  92.            time?
  93.            time-type
  94.            time-nanosecond
  95.            time-second
  96.            set-time-type!
  97.            set-time-nanosecond!
  98.            set-time-second!
  99.            copy-time
  100.            ;; Time comparison procedures
  101.            time<=?
  102.            time<?
  103.            time=?
  104.            time>=?
  105.            time>?
  106.            ;; Time arithmetic procedures
  107.            time-difference
  108.            time-difference!
  109.            add-duration
  110.            add-duration!
  111.            subtract-duration
  112.            subtract-duration!
  113.            ;; Date object and accessors
  114.            make-date
  115.            date?
  116.            date-nanosecond
  117.            date-second
  118.            date-minute
  119.            date-hour
  120.            date-day
  121.            date-month
  122.            date-year
  123.            date-zone-offset
  124.            date-year-day
  125.            date-week-day
  126.            date-week-number
  127.            ;; Time/Date/Julian Day/Modified Julian Day converters
  128.            date->julian-day
  129.            date->modified-julian-day
  130.            date->time-monotonic
  131.            date->time-tai
  132.            date->time-utc
  133.            julian-day->date
  134.            julian-day->time-monotonic
  135.            julian-day->time-tai
  136.            julian-day->time-utc
  137.            modified-julian-day->date
  138.            modified-julian-day->time-monotonic
  139.            modified-julian-day->time-tai
  140.            modified-julian-day->time-utc
  141.            time-monotonic->date
  142.            time-monotonic->time-tai
  143.            time-monotonic->time-tai!
  144.            time-monotonic->time-utc
  145.            time-monotonic->time-utc!
  146.            time-tai->date
  147.            time-tai->julian-day
  148.            time-tai->modified-julian-day
  149.            time-tai->time-monotonic
  150.            time-tai->time-monotonic!
  151.            time-tai->time-utc
  152.            time-tai->time-utc!
  153.            time-utc->date
  154.            time-utc->julian-day
  155.            time-utc->modified-julian-day
  156.            time-utc->time-monotonic
  157.            time-utc->time-monotonic!
  158.            time-utc->time-tai
  159.            time-utc->time-tai!
  160.            ;; Date to string/string to date converters.
  161.            date->string
  162.            string->date)
  163.  
  164. (cond-expand-provide (current-module) '(srfi-19))
  165.  
  166. (define time-tai 'time-tai)
  167. (define time-utc 'time-utc)
  168. (define time-monotonic 'time-monotonic)
  169. (define time-thread 'time-thread)
  170. (define time-process 'time-process)
  171. (define time-duration 'time-duration)
  172.  
  173. ;; FIXME: do we want to add gc time?
  174. ;; (define time-gc 'time-gc)
  175.  
  176. ;;-- LOCALE dependent constants
  177.  
  178. (define priv:locale-number-separator ".")
  179.  
  180. (define priv:locale-abbr-weekday-vector
  181.   (vector "Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat"))
  182.  
  183. (define priv:locale-long-weekday-vector
  184.   (vector
  185.    "Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday"))
  186.  
  187. ;; note empty string in 0th place.
  188. (define priv:locale-abbr-month-vector
  189.   (vector ""
  190.           "Jan"
  191.           "Feb"
  192.           "Mar"
  193.           "Apr"
  194.           "May"
  195.           "Jun"
  196.           "Jul"
  197.           "Aug"
  198.           "Sep"
  199.           "Oct"
  200.           "Nov"
  201.           "Dec"))
  202.  
  203. (define priv:locale-long-month-vector
  204.   (vector ""
  205.           "January"
  206.           "February"
  207.           "March"
  208.           "April"
  209.           "May"
  210.           "June"
  211.           "July"
  212.           "August"
  213.           "September"
  214.           "October"
  215.           "November"
  216.           "December"))
  217.  
  218. (define priv:locale-pm "PM")
  219. (define priv:locale-am "AM")
  220.  
  221. ;; See date->string
  222. (define priv:locale-date-time-format "~a ~b ~d ~H:~M:~S~z ~Y")
  223. (define priv:locale-short-date-format "~m/~d/~y")
  224. (define priv:locale-time-format "~H:~M:~S")
  225. (define priv:iso-8601-date-time-format "~Y-~m-~dT~H:~M:~S~z")
  226.  
  227. ;;-- Miscellaneous Constants.
  228. ;;-- only the priv:tai-epoch-in-jd might need changing if
  229. ;;   a different epoch is used.
  230.  
  231. (define priv:nano 1000000000)           ; nanoseconds in a second
  232. (define priv:sid  86400)                ; seconds in a day
  233. (define priv:sihd 43200)                ; seconds in a half day
  234. (define priv:tai-epoch-in-jd 4881175/2) ; julian day number for 'the epoch'
  235.  
  236. ;; FIXME: should this be something other than misc-error?
  237. (define (priv:time-error caller type value)
  238.   (if value
  239.       (throw 'misc-error caller "TIME-ERROR type ~A: ~S" (list type value) #f)
  240.       (throw 'misc-error caller "TIME-ERROR type ~A" (list type) #f)))
  241.  
  242. ;; A table of leap seconds
  243. ;; See ftp://maia.usno.navy.mil/ser7/tai-utc.dat
  244. ;; and update as necessary.
  245. ;; this procedures reads the file in the abover
  246. ;; format and creates the leap second table
  247. ;; it also calls the almost standard, but not R5 procedures read-line
  248. ;; & open-input-string
  249. ;; ie (set! priv:leap-second-table (priv:read-tai-utc-date "tai-utc.dat"))
  250.  
  251. (define (priv:read-tai-utc-data filename)
  252.   (define (convert-jd jd)
  253.     (* (- (inexact->exact jd) priv:tai-epoch-in-jd) priv:sid))
  254.   (define (convert-sec sec)
  255.     (inexact->exact sec))
  256.   (let ((port (open-input-file filename))
  257.         (table '()))
  258.     (let loop ((line (read-line port)))
  259.       (if (not (eq? line eof))
  260.           (begin
  261.             (let* ((data (read (open-input-string
  262.                                 (string-append "(" line ")"))))
  263.                    (year (car data))
  264.                    (jd   (cadddr (cdr data)))
  265.                    (secs (cadddr (cdddr data))))
  266.               (if (>= year 1972)
  267.                   (set! table (cons
  268.                                (cons (convert-jd jd) (convert-sec secs))
  269.                                table)))
  270.               (loop (read-line port))))))
  271.     table))
  272.  
  273. ;; each entry is (tai seconds since epoch . # seconds to subtract for utc)
  274. ;; note they go higher to lower, and end in 1972.
  275. (define priv:leap-second-table
  276.   '((915148800 . 32)
  277.     (867715200 . 31)
  278.     (820454400 . 30)
  279.     (773020800 . 29)
  280.     (741484800 . 28)
  281.     (709948800 . 27)
  282.     (662688000 . 26)
  283.     (631152000 . 25)
  284.     (567993600 . 24)
  285.     (489024000 . 23)
  286.     (425865600 . 22)
  287.     (394329600 . 21)
  288.     (362793600 . 20)
  289.     (315532800 . 19)
  290.     (283996800 . 18)
  291.     (252460800 . 17)
  292.     (220924800 . 16)
  293.     (189302400 . 15)
  294.     (157766400 . 14)
  295.     (126230400 . 13)
  296.     (94694400  . 12)
  297.     (78796800  . 11)
  298.     (63072000  . 10)))
  299.  
  300. (define (read-leap-second-table filename)
  301.   (set! priv:leap-second-table (priv:read-tai-utc-data filename))
  302.   (values))
  303.  
  304.  
  305. (define (priv:leap-second-delta utc-seconds)
  306.   (letrec ((lsd (lambda (table)
  307.                   (cond ((>= utc-seconds (caar table))
  308.                          (cdar table))
  309.                         (else (lsd (cdr table)))))))
  310.     (if (< utc-seconds  (* (- 1972 1970) 365 priv:sid)) 0
  311.         (lsd  priv:leap-second-table))))
  312.  
  313.  
  314. ;;; the TIME structure; creates the accessors, too.
  315.  
  316. (define-record-type time
  317.   (make-time-unnormalized type nanosecond second)
  318.   time?
  319.   (type time-type set-time-type!)
  320.   (nanosecond time-nanosecond set-time-nanosecond!)
  321.   (second time-second set-time-second!))
  322.  
  323. (define (copy-time time)
  324.   (make-time (time-type time) (time-nanosecond time) (time-second time)))
  325.  
  326. (define (priv:split-real r)
  327.   (if (integer? r)
  328.       (values (inexact->exact r) 0)
  329.       (let ((l (truncate r)))
  330.         (values (inexact->exact l) (- r l)))))
  331.  
  332. (define (priv:time-normalize! t)
  333.   (if (>= (abs (time-nanosecond t)) 1000000000)
  334.       (receive (int frac)
  335.       (priv:split-real (time-nanosecond t))
  336.     (set-time-second! t (+ (time-second t)
  337.                    (quotient int 1000000000)))
  338.     (set-time-nanosecond! t (+ (remainder int 1000000000)
  339.                    frac))))
  340.   (if (and (positive? (time-second t))
  341.            (negative? (time-nanosecond t)))
  342.       (begin
  343.         (set-time-second! t (- (time-second t) 1))
  344.         (set-time-nanosecond! t (+ 1000000000 (time-nanosecond t))))
  345.       (if (and (negative? (time-second t))
  346.                (positive? (time-nanosecond t)))
  347.           (begin
  348.             (set-time-second! t (+ (time-second t) 1))
  349.             (set-time-nanosecond! t (+ 1000000000 (time-nanosecond t))))))
  350.   t)
  351.  
  352. (define (make-time type nanosecond second)
  353.   (priv:time-normalize! (make-time-unnormalized type nanosecond second)))
  354.  
  355. ;; Helpers
  356. ;; FIXME: finish this and publish it?
  357. (define (date->broken-down-time date)
  358.   (let ((result (mktime 0)))
  359.     ;; FIXME: What should we do about leap-seconds which may overflow
  360.     ;; set-tm:sec?
  361.     (set-tm:sec result (date-second date))
  362.     (set-tm:min result (date-minute date))
  363.     (set-tm:hour result (date-hour date))
  364.     ;; FIXME: SRFI day ranges from 0-31.  (not compatible with set-tm:mday).
  365.     (set-tm:mday result (date-day date))
  366.     (set-tm:month result (- (date-month date) 1))
  367.     ;; FIXME: need to signal error on range violation.
  368.     (set-tm:year result (+ 1900 (date-year date)))
  369.     (set-tm:isdst result -1)
  370.     (set-tm:gmtoff result (- (date-zone-offset date)))
  371.     result))
  372.  
  373. ;;; current-time
  374.  
  375. ;;; specific time getters.
  376.  
  377. (define (priv:current-time-utc)
  378.   ;; Resolution is microseconds.
  379.   (let ((tod (gettimeofday)))
  380.     (make-time time-utc (* (cdr tod) 1000) (car tod))))
  381.  
  382. (define (priv:current-time-tai)
  383.   ;; Resolution is microseconds.
  384.   (let* ((tod (gettimeofday))
  385.          (sec (car tod))
  386.          (usec (cdr tod)))
  387.     (make-time time-tai
  388.                (* usec 1000)
  389.                (+ (car tod) (priv:leap-second-delta sec)))))
  390.  
  391. ;;(define (priv:current-time-ms-time time-type proc)
  392. ;;  (let ((current-ms (proc)))
  393. ;;    (make-time time-type
  394. ;;               (quotient current-ms 10000)
  395. ;;       (* (remainder current-ms 1000) 10000))))
  396.  
  397. ;; -- we define it to be the same as TAI.
  398. ;;    A different implemation of current-time-montonic
  399. ;;    will require rewriting all of the time-monotonic converters,
  400. ;;    of course.
  401.  
  402. (define (priv:current-time-monotonic)
  403.   ;; Resolution is microseconds.
  404.   (priv:current-time-tai))
  405.  
  406. (define (priv:current-time-thread)
  407.   (priv:time-error 'current-time 'unsupported-clock-type 'time-thread))
  408.  
  409. (define priv:ns-per-guile-tick (/ 1000000000 internal-time-units-per-second))
  410.  
  411. (define (priv:current-time-process)
  412.   (let ((run-time (get-internal-run-time)))
  413.     (make-time
  414.      time-process
  415.      (quotient run-time internal-time-units-per-second)
  416.      (* (remainder run-time internal-time-units-per-second)
  417.         priv:ns-per-guile-tick))))
  418.  
  419. (define (priv:current-time-process)
  420.   (let ((run-time (get-internal-run-time)))
  421.     (list
  422.      'time-process
  423.      (* (remainder run-time internal-time-units-per-second)
  424.         priv:ns-per-guile-tick)
  425.      (quotient run-time internal-time-units-per-second))))
  426.  
  427. ;;(define (priv:current-time-gc)
  428. ;;  (priv:current-time-ms-time time-gc current-gc-milliseconds))
  429.  
  430. (define (current-time . clock-type)
  431.   (let ((clock-type (if (null? clock-type) time-utc (car clock-type))))
  432.     (cond
  433.      ((eq? clock-type time-tai) (priv:current-time-tai))
  434.      ((eq? clock-type time-utc) (priv:current-time-utc))
  435.      ((eq? clock-type time-monotonic) (priv:current-time-monotonic))
  436.      ((eq? clock-type time-thread) (priv:current-time-thread))
  437.      ((eq? clock-type time-process) (priv:current-time-process))
  438.      ;;     ((eq? clock-type time-gc) (priv:current-time-gc))
  439.      (else (priv:time-error 'current-time 'invalid-clock-type clock-type)))))
  440.  
  441. ;; -- Time Resolution
  442. ;; This is the resolution of the clock in nanoseconds.
  443. ;; This will be implementation specific.
  444.  
  445. (define (time-resolution . clock-type)
  446.   (let ((clock-type (if (null? clock-type) time-utc (car clock-type))))
  447.     (case clock-type
  448.       ((time-tai) 1000)
  449.       ((time-utc) 1000)
  450.       ((time-monotonic) 1000)
  451.       ((time-process) priv:ns-per-guile-tick)
  452.       ;;     ((eq? clock-type time-thread) 1000)
  453.       ;;     ((eq? clock-type time-gc) 10000)
  454.       (else (priv:time-error 'time-resolution 'invalid-clock-type clock-type)))))
  455.  
  456. ;; -- Time comparisons
  457.  
  458. (define (time=? t1 t2)
  459.   ;; Arrange tests for speed and presume that t1 and t2 are actually times.
  460.   ;; also presume it will be rare to check two times of different types.
  461.   (and (= (time-second t1) (time-second t2))
  462.        (= (time-nanosecond t1) (time-nanosecond t2))
  463.        (eq? (time-type t1) (time-type t2))))
  464.  
  465. (define (time>? t1 t2)
  466.   (or (> (time-second t1) (time-second t2))
  467.       (and (= (time-second t1) (time-second t2))
  468.            (> (time-nanosecond t1) (time-nanosecond t2)))))
  469.  
  470. (define (time<? t1 t2)
  471.   (or (< (time-second t1) (time-second t2))
  472.       (and (= (time-second t1) (time-second t2))
  473.            (< (time-nanosecond t1) (time-nanosecond t2)))))
  474.  
  475. (define (time>=? t1 t2)
  476.   (or (> (time-second t1) (time-second t2))
  477.       (and (= (time-second t1) (time-second t2))
  478.            (>= (time-nanosecond t1) (time-nanosecond t2)))))
  479.  
  480. (define (time<=? t1 t2)
  481.   (or (< (time-second t1) (time-second t2))
  482.       (and (= (time-second t1) (time-second t2))
  483.            (<= (time-nanosecond t1) (time-nanosecond t2)))))
  484.  
  485. ;; -- Time arithmetic
  486.  
  487. (define (time-difference! time1 time2)
  488.   (let ((sec-diff (- (time-second time1) (time-second time2)))
  489.         (nsec-diff (- (time-nanosecond time1) (time-nanosecond time2))))
  490.     (set-time-type! time1 time-duration)
  491.     (set-time-second! time1 sec-diff)
  492.     (set-time-nanosecond! time1 nsec-diff)
  493.     (priv:time-normalize! time1)))
  494.  
  495. (define (time-difference time1 time2)
  496.   (let ((result (copy-time time1)))
  497.     (time-difference! result time2)))
  498.  
  499. (define (add-duration! t duration)
  500.   (if (not (eq? (time-type duration) time-duration))
  501.       (priv:time-error 'add-duration 'not-duration duration)
  502.       (let ((sec-plus (+ (time-second t) (time-second duration)))
  503.             (nsec-plus (+ (time-nanosecond t) (time-nanosecond duration))))
  504.         (set-time-second! t sec-plus)
  505.         (set-time-nanosecond! t nsec-plus)
  506.         (priv:time-normalize! t))))
  507.  
  508. (define (add-duration t duration)
  509.   (let ((result (copy-time t)))
  510.     (add-duration! result duration)))
  511.  
  512. (define (subtract-duration! t duration)
  513.   (if (not (eq? (time-type duration) time-duration))
  514.       (priv:time-error 'add-duration 'not-duration duration)
  515.       (let ((sec-minus  (- (time-second t) (time-second duration)))
  516.             (nsec-minus (- (time-nanosecond t) (time-nanosecond duration))))
  517.         (set-time-second! t sec-minus)
  518.         (set-time-nanosecond! t nsec-minus)
  519.         (priv:time-normalize! t))))
  520.  
  521. (define (subtract-duration time1 duration)
  522.   (let ((result (copy-time time1)))
  523.     (subtract-duration! result duration)))
  524.  
  525. ;; -- Converters between types.
  526.  
  527. (define (priv:time-tai->time-utc! time-in time-out caller)
  528.   (if (not (eq? (time-type time-in) time-tai))
  529.       (priv:time-error caller 'incompatible-time-types time-in))
  530.   (set-time-type! time-out time-utc)
  531.   (set-time-nanosecond! time-out (time-nanosecond time-in))
  532.   (set-time-second!     time-out (- (time-second time-in)
  533.                                     (priv:leap-second-delta
  534.                                      (time-second time-in))))
  535.   time-out)
  536.  
  537. (define (time-tai->time-utc time-in)
  538.   (priv:time-tai->time-utc! time-in (make-time-unnormalized #f #f #f) 'time-tai->time-utc))
  539.  
  540.  
  541. (define (time-tai->time-utc! time-in)
  542.   (priv:time-tai->time-utc! time-in time-in 'time-tai->time-utc!))
  543.  
  544. (define (priv:time-utc->time-tai! time-in time-out caller)
  545.   (if (not (eq? (time-type time-in) time-utc))
  546.       (priv:time-error caller 'incompatible-time-types time-in))
  547.   (set-time-type! time-out time-tai)
  548.   (set-time-nanosecond! time-out (time-nanosecond time-in))
  549.   (set-time-second!     time-out (+ (time-second time-in)
  550.                                     (priv:leap-second-delta
  551.                                      (time-second time-in))))
  552.   time-out)
  553.  
  554. (define (time-utc->time-tai time-in)
  555.   (priv:time-utc->time-tai! time-in (make-time-unnormalized #f #f #f) 'time-utc->time-tai))
  556.  
  557. (define (time-utc->time-tai! time-in)
  558.   (priv:time-utc->time-tai! time-in time-in 'time-utc->time-tai!))
  559.  
  560. ;; -- these depend on time-monotonic having the same definition as time-tai!
  561. (define (time-monotonic->time-utc time-in)
  562.   (if (not (eq? (time-type time-in) time-monotonic))
  563.       (priv:time-error caller 'incompatible-time-types time-in))
  564.   (let ((ntime (copy-time time-in)))
  565.     (set-time-type! ntime time-tai)
  566.     (priv:time-tai->time-utc! ntime ntime 'time-monotonic->time-utc)))
  567.  
  568. (define (time-monotonic->time-utc! time-in)
  569.   (if (not (eq? (time-type time-in) time-monotonic))
  570.       (priv:time-error caller 'incompatible-time-types time-in))
  571.   (set-time-type! time-in time-tai)
  572.   (priv:time-tai->time-utc! ntime ntime 'time-monotonic->time-utc))
  573.  
  574. (define (time-monotonic->time-tai time-in)
  575.   (if (not (eq? (time-type time-in) time-monotonic))
  576.       (priv:time-error caller 'incompatible-time-types time-in))
  577.   (let ((ntime (copy-time time-in)))
  578.     (set-time-type! ntime time-tai)
  579.     ntime))
  580.  
  581. (define (time-monotonic->time-tai! time-in)
  582.   (if (not (eq? (time-type time-in) time-monotonic))
  583.       (priv:time-error caller 'incompatible-time-types time-in))
  584.   (set-time-type! time-in time-tai)
  585.   time-in)
  586.  
  587. (define (time-utc->time-monotonic time-in)
  588.   (if (not (eq? (time-type time-in) time-utc))
  589.       (priv:time-error caller 'incompatible-time-types time-in))
  590.   (let ((ntime (priv:time-utc->time-tai! time-in (make-time-unnormalized #f #f #f)
  591.                                          'time-utc->time-monotonic)))
  592.     (set-time-type! ntime time-monotonic)
  593.     ntime))
  594.  
  595. (define (time-utc->time-monotonic! time-in)
  596.   (if (not (eq? (time-type time-in) time-utc))
  597.       (priv:time-error caller 'incompatible-time-types time-in))
  598.   (let ((ntime (priv:time-utc->time-tai! time-in time-in
  599.                                          'time-utc->time-monotonic!)))
  600.     (set-time-type! ntime time-monotonic)
  601.     ntime))
  602.  
  603. (define (time-tai->time-monotonic time-in)
  604.   (if (not (eq? (time-type time-in) time-tai))
  605.       (priv:time-error caller 'incompatible-time-types time-in))
  606.   (let ((ntime (copy-time time-in)))
  607.     (set-time-type! ntime time-monotonic)
  608.     ntime))
  609.  
  610. (define (time-tai->time-monotonic! time-in)
  611.   (if (not (eq? (time-type time-in) time-tai))
  612.       (priv:time-error caller 'incompatible-time-types time-in))
  613.   (set-time-type! time-in time-monotonic)
  614.   time-in)
  615.  
  616. ;; -- Date Structures
  617.  
  618. ;; FIXME: to be really safe, perhaps we should normalize the
  619. ;; seconds/nanoseconds/minutes coming in to make-date...
  620.  
  621. (define-record-type date
  622.   (make-date nanosecond second minute
  623.              hour day month
  624.              year
  625.              zone-offset)
  626.   date?
  627.   (nanosecond date-nanosecond set-date-nanosecond!)
  628.   (second date-second set-date-second!)
  629.   (minute date-minute set-date-minute!)
  630.   (hour date-hour set-date-hour!)
  631.   (day date-day set-date-day!)
  632.   (month date-month set-date-month!)
  633.   (year date-year set-date-year!)
  634.   (zone-offset date-zone-offset set-date-zone-offset!))
  635.  
  636. ;; gives the julian day which starts at noon.
  637. (define (priv:encode-julian-day-number day month year)
  638.   (let* ((a (quotient (- 14 month) 12))
  639.          (y (- (+ year 4800) a (if (negative? year) -1  0)))
  640.          (m (- (+ month (* 12 a)) 3)))
  641.     (+ day
  642.        (quotient (+ (* 153 m) 2) 5)
  643.        (* 365 y)
  644.        (quotient y 4)
  645.        (- (quotient y 100))
  646.        (quotient y 400)
  647.        -32045)))
  648.  
  649. ;; gives the seconds/date/month/year
  650. (define (priv:decode-julian-day-number jdn)
  651.   (let* ((days (inexact->exact (truncate jdn)))
  652.          (a (+ days 32044))
  653.          (b (quotient (+ (* 4 a) 3) 146097))
  654.          (c (- a (quotient (* 146097 b) 4)))
  655.          (d (quotient (+ (* 4 c) 3) 1461))
  656.          (e (- c (quotient (* 1461 d) 4)))
  657.          (m (quotient (+ (* 5 e) 2) 153))
  658.          (y (+ (* 100 b) d -4800 (quotient m 10))))
  659.     (values ; seconds date month year
  660.      (* (- jdn days) priv:sid)
  661.      (+ e (- (quotient (+ (* 153 m) 2) 5)) 1)
  662.      (+ m 3 (* -12 (quotient m 10)))
  663.      (if (>= 0 y) (- y 1) y))))
  664.  
  665. ;; relies on the fact that we named our time zone accessor
  666. ;; differently from MzScheme's....
  667. ;; This should be written to be OS specific.
  668.  
  669. (define (priv:local-tz-offset utc-time)
  670.   ;; SRFI uses seconds West, but guile (and libc) use seconds East.
  671.   (- (tm:gmtoff (localtime (time-second utc-time)))))
  672.  
  673. ;; special thing -- ignores nanos
  674. (define (priv:time->julian-day-number seconds tz-offset)
  675.   (+ (/ (+ seconds tz-offset priv:sihd)
  676.         priv:sid)
  677.      priv:tai-epoch-in-jd))
  678.  
  679. (define (priv:leap-second? second)
  680.   (and (assoc second priv:leap-second-table) #t))
  681.  
  682. (define (time-utc->date time . tz-offset)
  683.   (if (not (eq? (time-type time) time-utc))
  684.       (priv:time-error 'time->date 'incompatible-time-types  time))
  685.   (let* ((offset (if (null? tz-offset)
  686.              (priv:local-tz-offset time)
  687.              (car tz-offset)))
  688.          (leap-second? (priv:leap-second? (+ offset (time-second time))))
  689.          (jdn (priv:time->julian-day-number (if leap-second?
  690.                                                 (- (time-second time) 1)
  691.                                                 (time-second time))
  692.                                             offset)))
  693.  
  694.     (call-with-values (lambda () (priv:decode-julian-day-number jdn))
  695.       (lambda (secs date month year)
  696.     ;; secs is a real because jdn is a real in Guile;
  697.     ;; but it is conceptionally an integer.
  698.         (let* ((int-secs (inexact->exact (round secs)))
  699.                (hours    (quotient int-secs (* 60 60)))
  700.                (rem      (remainder int-secs (* 60 60)))
  701.                (minutes  (quotient rem 60))
  702.                (seconds  (remainder rem 60)))
  703.           (make-date (time-nanosecond time)
  704.                      (if leap-second? (+ seconds 1) seconds)
  705.                      minutes
  706.                      hours
  707.                      date
  708.                      month
  709.                      year
  710.                      offset))))))
  711.  
  712. (define (time-tai->date time  . tz-offset)
  713.   (if (not (eq? (time-type time) time-tai))
  714.       (priv:time-error 'time->date 'incompatible-time-types  time))
  715.   (let* ((offset (if (null? tz-offset)
  716.              (priv:local-tz-offset (time-tai->time-utc time))
  717.              (car tz-offset)))
  718.          (seconds (- (time-second time)
  719.                      (priv:leap-second-delta (time-second time))))
  720.          (leap-second? (priv:leap-second? (+ offset seconds)))
  721.          (jdn (priv:time->julian-day-number (if leap-second?
  722.                                                 (- seconds 1)
  723.                                                 seconds)
  724.                                             offset)))
  725.     (call-with-values (lambda () (priv:decode-julian-day-number jdn))
  726.       (lambda (secs date month year)
  727.     ;; secs is a real because jdn is a real in Guile;
  728.     ;; but it is conceptionally an integer.
  729.         ;; adjust for leap seconds if necessary ...
  730.         (let* ((int-secs (inexact->exact (round secs)))
  731.            (hours    (quotient int-secs (* 60 60)))
  732.                (rem      (remainder int-secs (* 60 60)))
  733.                (minutes  (quotient rem 60))
  734.                (seconds  (remainder rem 60)))
  735.           (make-date (time-nanosecond time)
  736.                      (if leap-second? (+ seconds 1) seconds)
  737.                      minutes
  738.                      hours
  739.                      date
  740.                      month
  741.                      year
  742.                      offset))))))
  743.  
  744. ;; this is the same as time-tai->date.
  745. (define (time-monotonic->date time . tz-offset)
  746.   (if (not (eq? (time-type time) time-monotonic))
  747.       (priv:time-error 'time->date 'incompatible-time-types  time))
  748.   (let* ((offset (if (null? tz-offset)
  749.              (priv:local-tz-offset (time-monotonic->time-utc time))
  750.              (car tz-offset)))
  751.          (seconds (- (time-second time)
  752.                      (priv:leap-second-delta (time-second time))))
  753.          (leap-second? (priv:leap-second? (+ offset seconds)))
  754.          (jdn (priv:time->julian-day-number (if leap-second?
  755.                                                 (- seconds 1)
  756.                                                 seconds)
  757.                                             offset)))
  758.     (call-with-values (lambda () (priv:decode-julian-day-number jdn))
  759.       (lambda (secs date month year)
  760.     ;; secs is a real because jdn is a real in Guile;
  761.     ;; but it is conceptionally an integer.
  762.         ;; adjust for leap seconds if necessary ...
  763.         (let* ((int-secs (inexact->exact (round secs)))
  764.            (hours    (quotient int-secs (* 60 60)))
  765.                (rem      (remainder int-secs (* 60 60)))
  766.                (minutes  (quotient rem 60))
  767.                (seconds  (remainder rem 60)))
  768.           (make-date (time-nanosecond time)
  769.                      (if leap-second? (+ seconds 1) seconds)
  770.                      minutes
  771.                      hours
  772.                      date
  773.                      month
  774.                      year
  775.                      offset))))))
  776.  
  777. (define (date->time-utc date)
  778.   (let* ((jdays (- (priv:encode-julian-day-number (date-day date)
  779.                                                  (date-month date)
  780.                                                  (date-year date))
  781.            priv:tai-epoch-in-jd))
  782.      ;; jdays is an integer plus 1/2,
  783.      (jdays-1/2 (inexact->exact (- jdays 1/2))))
  784.     (make-time
  785.      time-utc
  786.      (date-nanosecond date)
  787.      (+ (* jdays-1/2 24 60 60)
  788.         (* (date-hour date) 60 60)
  789.         (* (date-minute date) 60)
  790.         (date-second date)
  791.     (- (date-zone-offset date))))))
  792.  
  793. (define (date->time-tai date)
  794.   (time-utc->time-tai! (date->time-utc date)))
  795.  
  796. (define (date->time-monotonic date)
  797.   (time-utc->time-monotonic! (date->time-utc date)))
  798.  
  799. (define (priv:leap-year? year)
  800.   (or (= (modulo year 400) 0)
  801.       (and (= (modulo year 4) 0) (not (= (modulo year 100) 0)))))
  802.  
  803. (define (leap-year? date)
  804.   (priv:leap-year? (date-year date)))
  805.  
  806. ;; Map 1-based month number M to number of days in the year before the
  807. ;; start of month M (in a non-leap year).
  808. (define priv:month-assoc '((1 . 0)   (2 . 31)   (3 . 59)   (4 . 90)
  809.                (5 . 120) (6 . 151)  (7 . 181)  (8 . 212)
  810.                (9 . 243) (10 . 273) (11 . 304) (12 . 334)))
  811.  
  812. (define (priv:year-day day month year)
  813.   (let ((days-pr (assoc month priv:month-assoc)))
  814.     (if (not days-pr)
  815.         (priv:error 'date-year-day 'invalid-month-specification month))
  816.     (if (and (priv:leap-year? year) (> month 2))
  817.         (+ day (cdr days-pr) 1)
  818.         (+ day (cdr days-pr)))))
  819.  
  820. (define (date-year-day date)
  821.   (priv:year-day (date-day date) (date-month date) (date-year date)))
  822.  
  823. ;; from calendar faq
  824. (define (priv:week-day day month year)
  825.   (let* ((a (quotient (- 14 month) 12))
  826.          (y (- year a))
  827.          (m (+ month (* 12 a) -2)))
  828.     (modulo (+ day
  829.                y
  830.                (quotient y 4)
  831.                (- (quotient y 100))
  832.                (quotient y 400)
  833.                (quotient (* 31 m) 12))
  834.             7)))
  835.  
  836. (define (date-week-day date)
  837.   (priv:week-day (date-day date) (date-month date) (date-year date)))
  838.  
  839. (define (priv:days-before-first-week date day-of-week-starting-week)
  840.   (let* ((first-day (make-date 0 0 0 0
  841.                                1
  842.                                1
  843.                                (date-year date)
  844.                                #f))
  845.          (fdweek-day (date-week-day first-day)))
  846.     (modulo (- day-of-week-starting-week fdweek-day)
  847.             7)))
  848.  
  849. (define (date-week-number date day-of-week-starting-week)
  850.   (quotient (- (date-year-day date)
  851.                (priv:days-before-first-week  date day-of-week-starting-week))
  852.             7))
  853.  
  854. (define (current-date . tz-offset)
  855.   (let ((time (current-time time-utc)))
  856.     (time-utc->date
  857.      time
  858.      (if (null? tz-offset)
  859.      (priv:local-tz-offset time)
  860.      (car tz-offset)))))
  861.  
  862. ;; given a 'two digit' number, find the year within 50 years +/-
  863. (define (priv:natural-year n)
  864.   (let* ((current-year (date-year (current-date)))
  865.          (current-century (* (quotient current-year 100) 100)))
  866.     (cond
  867.      ((>= n 100) n)
  868.      ((<  n 0) n)
  869.      ((<=  (- (+ current-century n) current-year) 50) (+ current-century n))
  870.      (else (+ (- current-century 100) n)))))
  871.  
  872. (define (date->julian-day date)
  873.   (let ((nanosecond (date-nanosecond date))
  874.         (second (date-second date))
  875.         (minute (date-minute date))
  876.         (hour (date-hour date))
  877.         (day (date-day date))
  878.         (month (date-month date))
  879.         (year (date-year date)))
  880.     (+ (priv:encode-julian-day-number day month year)
  881.        (- 1/2)
  882.        (+ (/ (+ (* hour 60 60)
  883.                 (* minute 60)
  884.                 second
  885.                 (/ nanosecond priv:nano))
  886.              priv:sid)))))
  887.  
  888. (define (date->modified-julian-day date)
  889.   (- (date->julian-day date)
  890.      4800001/2))
  891.  
  892. (define (time-utc->julian-day time)
  893.   (if (not (eq? (time-type time) time-utc))
  894.       (priv:time-error 'time->date 'incompatible-time-types  time))
  895.   (+ (/ (+ (time-second time) (/ (time-nanosecond time) priv:nano))
  896.         priv:sid)
  897.      priv:tai-epoch-in-jd))
  898.  
  899. (define (time-utc->modified-julian-day time)
  900.   (- (time-utc->julian-day time)
  901.      4800001/2))
  902.  
  903. (define (time-tai->julian-day time)
  904.   (if (not (eq? (time-type time) time-tai))
  905.       (priv:time-error 'time->date 'incompatible-time-types  time))
  906.   (+ (/ (+ (- (time-second time)
  907.               (priv:leap-second-delta (time-second time)))
  908.            (/ (time-nanosecond time) priv:nano))
  909.         priv:sid)
  910.      priv:tai-epoch-in-jd))
  911.  
  912. (define (time-tai->modified-julian-day time)
  913.   (- (time-tai->julian-day time)
  914.      4800001/2))
  915.  
  916. ;; this is the same as time-tai->julian-day
  917. (define (time-monotonic->julian-day time)
  918.   (if (not (eq? (time-type time) time-monotonic))
  919.       (priv:time-error 'time->date 'incompatible-time-types  time))
  920.   (+ (/ (+ (- (time-second time)
  921.               (priv:leap-second-delta (time-second time)))
  922.            (/ (time-nanosecond time) priv:nano))
  923.         priv:sid)
  924.      priv:tai-epoch-in-jd))
  925.  
  926. (define (time-monotonic->modified-julian-day time)
  927.   (- (time-monotonic->julian-day time)
  928.      4800001/2))
  929.  
  930. (define (julian-day->time-utc jdn)
  931.   (let ((secs (* priv:sid (- jdn priv:tai-epoch-in-jd))))
  932.     (receive (seconds parts)
  933.     (priv:split-real secs)
  934.       (make-time time-utc
  935.          (* parts priv:nano)
  936.          seconds))))
  937.  
  938. (define (julian-day->time-tai jdn)
  939.   (time-utc->time-tai! (julian-day->time-utc jdn)))
  940.  
  941. (define (julian-day->time-monotonic jdn)
  942.   (time-utc->time-monotonic! (julian-day->time-utc jdn)))
  943.  
  944. (define (julian-day->date jdn . tz-offset)
  945.   (let* ((time (julian-day->time-utc jdn))
  946.      (offset (if (null? tz-offset)
  947.              (priv:local-tz-offset time)
  948.              (car tz-offset))))
  949.     (time-utc->date time offset)))
  950.  
  951. (define (modified-julian-day->date jdn . tz-offset)
  952.   (apply julian-day->date (+ jdn 4800001/2)
  953.      tz-offset))
  954.  
  955. (define (modified-julian-day->time-utc jdn)
  956.   (julian-day->time-utc (+ jdn 4800001/2)))
  957.  
  958. (define (modified-julian-day->time-tai jdn)
  959.   (julian-day->time-tai (+ jdn 4800001/2)))
  960.  
  961. (define (modified-julian-day->time-monotonic jdn)
  962.   (julian-day->time-monotonic (+ jdn 4800001/2)))
  963.  
  964. (define (current-julian-day)
  965.   (time-utc->julian-day (current-time time-utc)))
  966.  
  967. (define (current-modified-julian-day)
  968.   (time-utc->modified-julian-day (current-time time-utc)))
  969.  
  970. ;; returns a string rep. of number N, of minimum LENGTH, padded with
  971. ;; character PAD-WITH. If PAD-WITH is #f, no padding is done, and it's
  972. ;; as if number->string was used.  if string is longer than or equal
  973. ;; in length to LENGTH, it's as if number->string was used.
  974.  
  975. (define (priv:padding n pad-with length)
  976.   (let* ((str (number->string n))
  977.          (str-len (string-length str)))
  978.     (if (or (>= str-len length)
  979.             (not pad-with))
  980.         str
  981.         (string-append (make-string (- length str-len) pad-with) str))))
  982.  
  983. (define (priv:last-n-digits i n)
  984.   (abs (remainder i (expt 10 n))))
  985.  
  986. (define (priv:locale-abbr-weekday n)
  987.   (vector-ref priv:locale-abbr-weekday-vector n))
  988.  
  989. (define (priv:locale-long-weekday n)
  990.   (vector-ref priv:locale-long-weekday-vector n))
  991.  
  992. (define (priv:locale-abbr-month n)
  993.   (vector-ref priv:locale-abbr-month-vector n))
  994.  
  995. (define (priv:locale-long-month n)
  996.   (vector-ref priv:locale-long-month-vector n))
  997.  
  998. (define (priv:vector-find needle haystack comparator)
  999.   (let ((len (vector-length haystack)))
  1000.     (define (priv:vector-find-int index)
  1001.       (cond
  1002.        ((>= index len) #f)
  1003.        ((comparator needle (vector-ref haystack index)) index)
  1004.        (else (priv:vector-find-int (+ index 1)))))
  1005.     (priv:vector-find-int 0)))
  1006.  
  1007. (define (priv:locale-abbr-weekday->index string)
  1008.   (priv:vector-find string priv:locale-abbr-weekday-vector string=?))
  1009.  
  1010. (define (priv:locale-long-weekday->index string)
  1011.   (priv:vector-find string priv:locale-long-weekday-vector string=?))
  1012.  
  1013. (define (priv:locale-abbr-month->index string)
  1014.   (priv:vector-find string priv:locale-abbr-month-vector string=?))
  1015.  
  1016. (define (priv:locale-long-month->index string)
  1017.   (priv:vector-find string priv:locale-long-month-vector string=?))
  1018.  
  1019.  
  1020. ;; FIXME: mkoeppe: Put a symbolic time zone in the date structs.
  1021. ;; Print it here instead of the numerical offset if available.
  1022. (define (priv:locale-print-time-zone date port)
  1023.   (priv:tz-printer (date-zone-offset date) port))
  1024.  
  1025. ;; FIXME: we should use strftime to determine this dynamically if possible.
  1026. ;; Again, locale specific.
  1027. (define (priv:locale-am/pm hr)
  1028.   (if (> hr 11) priv:locale-pm priv:locale-am))
  1029.  
  1030. (define (priv:tz-printer offset port)
  1031.   (cond
  1032.    ((= offset 0) (display "Z" port))
  1033.    ((negative? offset) (display "-" port))
  1034.    (else (display "+" port)))
  1035.   (if (not (= offset 0))
  1036.       (let ((hours   (abs (quotient offset (* 60 60))))
  1037.             (minutes (abs (quotient (remainder offset (* 60 60)) 60))))
  1038.         (display (priv:padding hours #\0 2) port)
  1039.         (display (priv:padding minutes #\0 2) port))))
  1040.  
  1041. ;; A table of output formatting directives.
  1042. ;; the first time is the format char.
  1043. ;; the second is a procedure that takes the date, a padding character
  1044. ;; (which might be #f), and the output port.
  1045. ;;
  1046. (define priv:directives
  1047.   (list
  1048.    (cons #\~ (lambda (date pad-with port)
  1049.                (display #\~ port)))
  1050.    (cons #\a (lambda (date pad-with port)
  1051.                (display (priv:locale-abbr-weekday (date-week-day date))
  1052.                         port)))
  1053.    (cons #\A (lambda (date pad-with port)
  1054.                (display (priv:locale-long-weekday (date-week-day date))
  1055.                         port)))
  1056.    (cons #\b (lambda (date pad-with port)
  1057.                (display (priv:locale-abbr-month (date-month date))
  1058.                         port)))
  1059.    (cons #\B (lambda (date pad-with port)
  1060.                (display (priv:locale-long-month (date-month date))
  1061.                         port)))
  1062.    (cons #\c (lambda (date pad-with port)
  1063.                (display (date->string date priv:locale-date-time-format) port)))
  1064.    (cons #\d (lambda (date pad-with port)
  1065.                (display (priv:padding (date-day date)
  1066.                                       #\0 2)
  1067.                         port)))
  1068.    (cons #\D (lambda (date pad-with port)
  1069.                (display (date->string date "~m/~d/~y") port)))
  1070.    (cons #\e (lambda (date pad-with port)
  1071.                (display (priv:padding (date-day date)
  1072.                                       #\Space 2)
  1073.                         port)))
  1074.    (cons #\f (lambda (date pad-with port)
  1075.                (if (> (date-nanosecond date)
  1076.                       priv:nano)
  1077.                    (display (priv:padding (+ (date-second date) 1)
  1078.                                           pad-with 2)
  1079.                             port)
  1080.                    (display (priv:padding (date-second date)
  1081.                                           pad-with 2)
  1082.                             port))
  1083.                (receive (i f)
  1084.                         (priv:split-real (/
  1085.                                           (date-nanosecond date)
  1086.                                           priv:nano 1.0))
  1087.                         (let* ((ns (number->string f))
  1088.                                (le (string-length ns)))
  1089.                           (if (> le 2)
  1090.                               (begin
  1091.                                 (display priv:locale-number-separator port)
  1092.                                 (display (substring ns 2 le) port)))))))
  1093.    (cons #\h (lambda (date pad-with port)
  1094.                (display (date->string date "~b") port)))
  1095.    (cons #\H (lambda (date pad-with port)
  1096.                (display (priv:padding (date-hour date)
  1097.                                       pad-with 2)
  1098.                         port)))
  1099.    (cons #\I (lambda (date pad-with port)
  1100.                (let ((hr (date-hour date)))
  1101.                  (if (> hr 12)
  1102.                      (display (priv:padding (- hr 12)
  1103.                                             pad-with 2)
  1104.                               port)
  1105.                      (display (priv:padding hr
  1106.                                             pad-with 2)
  1107.                               port)))))
  1108.    (cons #\j (lambda (date pad-with port)
  1109.                (display (priv:padding (date-year-day date)
  1110.                                       pad-with 3)
  1111.                         port)))
  1112.    (cons #\k (lambda (date pad-with port)
  1113.                (display (priv:padding (date-hour date)
  1114.                                       #\Space 2)
  1115.                         port)))
  1116.    (cons #\l (lambda (date pad-with port)
  1117.                (let ((hr (if (> (date-hour date) 12)
  1118.                              (- (date-hour date) 12) (date-hour date))))
  1119.                  (display (priv:padding hr  #\Space 2)
  1120.                           port))))
  1121.    (cons #\m (lambda (date pad-with port)
  1122.                (display (priv:padding (date-month date)
  1123.                                       pad-with 2)
  1124.                         port)))
  1125.    (cons #\M (lambda (date pad-with port)
  1126.                (display (priv:padding (date-minute date)
  1127.                                       pad-with 2)
  1128.                         port)))
  1129.    (cons #\n (lambda (date pad-with port)
  1130.                (newline port)))
  1131.    (cons #\N (lambda (date pad-with port)
  1132.                (display (priv:padding (date-nanosecond date)
  1133.                                       pad-with 7)
  1134.                         port)))
  1135.    (cons #\p (lambda (date pad-with port)
  1136.                (display (priv:locale-am/pm (date-hour date)) port)))
  1137.    (cons #\r (lambda (date pad-with port)
  1138.                (display (date->string date "~I:~M:~S ~p") port)))
  1139.    (cons #\s (lambda (date pad-with port)
  1140.                (display (time-second (date->time-utc date)) port)))
  1141.    (cons #\S (lambda (date pad-with port)
  1142.                (if (> (date-nanosecond date)
  1143.                       priv:nano)
  1144.                    (display (priv:padding (+ (date-second date) 1)
  1145.                                           pad-with 2)
  1146.                             port)
  1147.                    (display (priv:padding (date-second date)
  1148.                                           pad-with 2)
  1149.                             port))))
  1150.    (cons #\t (lambda (date pad-with port)
  1151.                (display #\Tab port)))
  1152.    (cons #\T (lambda (date pad-with port)
  1153.                (display (date->string date "~H:~M:~S") port)))
  1154.    (cons #\U (lambda (date pad-with port)
  1155.                (if (> (priv:days-before-first-week date 0) 0)
  1156.                    (display (priv:padding (+ (date-week-number date 0) 1)
  1157.                                           #\0 2) port)
  1158.                    (display (priv:padding (date-week-number date 0)
  1159.                                           #\0 2) port))))
  1160.    (cons #\V (lambda (date pad-with port)
  1161.                (display (priv:padding (date-week-number date 1)
  1162.                                       #\0 2) port)))
  1163.    (cons #\w (lambda (date pad-with port)
  1164.                (display (date-week-day date) port)))
  1165.    (cons #\x (lambda (date pad-with port)
  1166.                (display (date->string date priv:locale-short-date-format) port)))
  1167.    (cons #\X (lambda (date pad-with port)
  1168.                (display (date->string date priv:locale-time-format) port)))
  1169.    (cons #\W (lambda (date pad-with port)
  1170.                (if (> (priv:days-before-first-week date 1) 0)
  1171.                    (display (priv:padding (+ (date-week-number date 1) 1)
  1172.                                           #\0 2) port)
  1173.                    (display (priv:padding (date-week-number date 1)
  1174.                                           #\0 2) port))))
  1175.    (cons #\y (lambda (date pad-with port)
  1176.                (display (priv:padding (priv:last-n-digits
  1177.                                        (date-year date) 2)
  1178.                                       pad-with
  1179.                                       2)
  1180.                         port)))
  1181.    (cons #\Y (lambda (date pad-with port)
  1182.                (display (date-year date) port)))
  1183.    (cons #\z (lambda (date pad-with port)
  1184.                (priv:tz-printer (date-zone-offset date) port)))
  1185.    (cons #\Z (lambda (date pad-with port)
  1186.                (priv:locale-print-time-zone date port)))
  1187.    (cons #\1 (lambda (date pad-with port)
  1188.                (display (date->string date "~Y-~m-~d") port)))
  1189.    (cons #\2 (lambda (date pad-with port)
  1190.                (display (date->string date "~k:~M:~S~z") port)))
  1191.    (cons #\3 (lambda (date pad-with port)
  1192.                (display (date->string date "~k:~M:~S") port)))
  1193.    (cons #\4 (lambda (date pad-with port)
  1194.                (display (date->string date "~Y-~m-~dT~k:~M:~S~z") port)))
  1195.    (cons #\5 (lambda (date pad-with port)
  1196.                (display (date->string date "~Y-~m-~dT~k:~M:~S") port)))))
  1197.  
  1198.  
  1199. (define (priv:get-formatter char)
  1200.   (let ((associated (assoc char priv:directives)))
  1201.     (if associated (cdr associated) #f)))
  1202.  
  1203. (define (priv:date-printer date index format-string str-len port)
  1204.   (if (>= index str-len)
  1205.       (values)
  1206.       (let ((current-char (string-ref format-string index)))
  1207.         (if (not (char=? current-char #\~))
  1208.             (begin
  1209.               (display current-char port)
  1210.               (priv:date-printer date (+ index 1) format-string str-len port))
  1211.             (if (= (+ index 1) str-len) ; bad format string.
  1212.                 (priv:time-error 'priv:date-printer 'bad-date-format-string
  1213.                                  format-string)
  1214.                 (let ((pad-char? (string-ref format-string (+ index 1))))
  1215.                   (cond
  1216.                    ((char=? pad-char? #\-)
  1217.                     (if (= (+ index 2) str-len) ; bad format string.
  1218.                         (priv:time-error 'priv:date-printer
  1219.                                          'bad-date-format-string
  1220.                                          format-string)
  1221.                         (let ((formatter (priv:get-formatter
  1222.                                           (string-ref format-string
  1223.                                                       (+ index 2)))))
  1224.                           (if (not formatter)
  1225.                               (priv:time-error 'priv:date-printer
  1226.                                                'bad-date-format-string
  1227.                                                format-string)
  1228.                               (begin
  1229.                                 (formatter date #f port)
  1230.                                 (priv:date-printer date
  1231.                                                    (+ index 3)
  1232.                                                    format-string
  1233.                                                    str-len
  1234.                                                    port))))))
  1235.  
  1236.                    ((char=? pad-char? #\_)
  1237.                     (if (= (+ index 2) str-len) ; bad format string.
  1238.                         (priv:time-error 'priv:date-printer
  1239.                                          'bad-date-format-string
  1240.                                          format-string)
  1241.                         (let ((formatter (priv:get-formatter
  1242.                                           (string-ref format-string
  1243.                                                       (+ index 2)))))
  1244.                           (if (not formatter)
  1245.                               (priv:time-error 'priv:date-printer
  1246.                                                'bad-date-format-string
  1247.                                                format-string)
  1248.                               (begin
  1249.                                 (formatter date #\Space port)
  1250.                                 (priv:date-printer date
  1251.                                                    (+ index 3)
  1252.                                                    format-string
  1253.                                                    str-len
  1254.                                                    port))))))
  1255.                    (else
  1256.                     (let ((formatter (priv:get-formatter
  1257.                                       (string-ref format-string
  1258.                                                   (+ index 1)))))
  1259.                       (if (not formatter)
  1260.                           (priv:time-error 'priv:date-printer
  1261.                                            'bad-date-format-string
  1262.                                            format-string)
  1263.                           (begin
  1264.                             (formatter date #\0 port)
  1265.                             (priv:date-printer date
  1266.                                                (+ index 2)
  1267.                                                format-string
  1268.                                                str-len
  1269.                                                port))))))))))))
  1270.  
  1271.  
  1272. (define (date->string date .  format-string)
  1273.   (let ((str-port (open-output-string))
  1274.         (fmt-str (if (null? format-string) "~c" (car format-string))))
  1275.     (priv:date-printer date 0 fmt-str (string-length fmt-str) str-port)
  1276.     (get-output-string str-port)))
  1277.  
  1278. (define (priv:char->int ch)
  1279.   (case ch
  1280.    ((#\0) 0)
  1281.    ((#\1) 1)
  1282.    ((#\2) 2)
  1283.    ((#\3) 3)
  1284.    ((#\4) 4)
  1285.    ((#\5) 5)
  1286.    ((#\6) 6)
  1287.    ((#\7) 7)
  1288.    ((#\8) 8)
  1289.    ((#\9) 9)
  1290.    (else (priv:time-error 'bad-date-template-string
  1291.                           (list "Non-integer character" ch i)))))
  1292.  
  1293. ;; read an integer upto n characters long on port; upto -> #f is any length
  1294. (define (priv:integer-reader upto port)
  1295.   (let loop ((accum 0) (nchars 0))
  1296.     (let ((ch (peek-char port)))
  1297.       (if (or (eof-object? ch)
  1298.               (not (char-numeric? ch))
  1299.               (and upto (>= nchars  upto)))
  1300.           accum
  1301.           (loop (+ (* accum 10) (priv:char->int (read-char port)))
  1302.                 (+ nchars 1))))))
  1303.  
  1304. (define (priv:make-integer-reader upto)
  1305.   (lambda (port)
  1306.     (priv:integer-reader upto port)))
  1307.  
  1308. ;; read *exactly* n characters and convert to integer; could be padded
  1309. (define (priv:integer-reader-exact n port)
  1310.   (let ((padding-ok #t))
  1311.     (define (accum-int port accum nchars)
  1312.       (let ((ch (peek-char port)))
  1313.     (cond
  1314.      ((>= nchars n) accum)
  1315.      ((eof-object? ch)
  1316.       (priv:time-error 'string->date 'bad-date-template-string
  1317.                            "Premature ending to integer read."))
  1318.      ((char-numeric? ch)
  1319.       (set! padding-ok #f)
  1320.       (accum-int port
  1321.                      (+ (* accum 10) (priv:char->int (read-char port)))
  1322.              (+ nchars 1)))
  1323.      (padding-ok
  1324.       (read-char port) ; consume padding
  1325.       (accum-int port accum (+ nchars 1)))
  1326.      (else ; padding where it shouldn't be
  1327.       (priv:time-error 'string->date 'bad-date-template-string
  1328.                            "Non-numeric characters in integer read.")))))
  1329.     (accum-int port 0 0)))
  1330.  
  1331.  
  1332. (define (priv:make-integer-exact-reader n)
  1333.   (lambda (port)
  1334.     (priv:integer-reader-exact n port)))
  1335.  
  1336. (define (priv:zone-reader port)
  1337.   (let ((offset 0)
  1338.         (positive? #f))
  1339.     (let ((ch (read-char port)))
  1340.       (if (eof-object? ch)
  1341.           (priv:time-error 'string->date 'bad-date-template-string
  1342.                            (list "Invalid time zone +/-" ch)))
  1343.       (if (or (char=? ch #\Z) (char=? ch #\z))
  1344.           0
  1345.           (begin
  1346.             (cond
  1347.              ((char=? ch #\+) (set! positive? #t))
  1348.              ((char=? ch #\-) (set! positive? #f))
  1349.              (else
  1350.               (priv:time-error 'string->date 'bad-date-template-string
  1351.                                (list "Invalid time zone +/-" ch))))
  1352.             (let ((ch (read-char port)))
  1353.               (if (eof-object? ch)
  1354.                   (priv:time-error 'string->date 'bad-date-template-string
  1355.                                    (list "Invalid time zone number" ch)))
  1356.               (set! offset (* (priv:char->int ch)
  1357.                               10 60 60)))
  1358.             (let ((ch (read-char port)))
  1359.               (if (eof-object? ch)
  1360.                   (priv:time-error 'string->date 'bad-date-template-string
  1361.                                    (list "Invalid time zone number" ch)))
  1362.               (set! offset (+ offset (* (priv:char->int ch)
  1363.                                         60 60))))
  1364.             (let ((ch (read-char port)))
  1365.               (if (eof-object? ch)
  1366.                   (priv:time-error 'string->date 'bad-date-template-string
  1367.                                    (list "Invalid time zone number" ch)))
  1368.               (set! offset (+ offset (* (priv:char->int ch)
  1369.                                         10 60))))
  1370.             (let ((ch (read-char port)))
  1371.               (if (eof-object? ch)
  1372.                   (priv:time-error 'string->date 'bad-date-template-string
  1373.                                    (list "Invalid time zone number" ch)))
  1374.               (set! offset (+ offset (* (priv:char->int ch)
  1375.                                         60))))
  1376.             (if positive? offset (- offset)))))))
  1377.  
  1378. ;; looking at a char, read the char string, run thru indexer, return index
  1379. (define (priv:locale-reader port indexer)
  1380.  
  1381.   (define (read-char-string result)
  1382.     (let ((ch (peek-char port)))
  1383.       (if (char-alphabetic? ch)
  1384.           (read-char-string (cons (read-char port) result))
  1385.           (list->string (reverse! result)))))
  1386.  
  1387.   (let* ((str (read-char-string '()))
  1388.          (index (indexer str)))
  1389.     (if index index (priv:time-error 'string->date
  1390.                                      'bad-date-template-string
  1391.                                      (list "Invalid string for " indexer)))))
  1392.  
  1393. (define (priv:make-locale-reader indexer)
  1394.   (lambda (port)
  1395.     (priv:locale-reader port indexer)))
  1396.  
  1397. (define (priv:make-char-id-reader char)
  1398.   (lambda (port)
  1399.     (if (char=? char (read-char port))
  1400.         char
  1401.         (priv:time-error 'string->date
  1402.                          'bad-date-template-string
  1403.                          "Invalid character match."))))
  1404.  
  1405. ;; A List of formatted read directives.
  1406. ;; Each entry is a list.
  1407. ;; 1. the character directive;
  1408. ;; a procedure, which takes a character as input & returns
  1409. ;; 2. #t as soon as a character on the input port is acceptable
  1410. ;; for input,
  1411. ;; 3. a port reader procedure that knows how to read the current port
  1412. ;; for a value. Its one parameter is the port.
  1413. ;; 4. a action procedure, that takes the value (from 3.) and some
  1414. ;; object (here, always the date) and (probably) side-effects it.
  1415. ;; In some cases (e.g., ~A) the action is to do nothing
  1416.  
  1417. (define priv:read-directives
  1418.   (let ((ireader4 (priv:make-integer-reader 4))
  1419.         (ireader2 (priv:make-integer-reader 2))
  1420.         (ireaderf (priv:make-integer-reader #f))
  1421.         (eireader2 (priv:make-integer-exact-reader 2))
  1422.         (eireader4 (priv:make-integer-exact-reader 4))
  1423.         (locale-reader-abbr-weekday (priv:make-locale-reader
  1424.                                      priv:locale-abbr-weekday->index))
  1425.         (locale-reader-long-weekday (priv:make-locale-reader
  1426.                                      priv:locale-long-weekday->index))
  1427.         (locale-reader-abbr-month   (priv:make-locale-reader
  1428.                                      priv:locale-abbr-month->index))
  1429.         (locale-reader-long-month   (priv:make-locale-reader
  1430.                                      priv:locale-long-month->index))
  1431.         (char-fail (lambda (ch) #t))
  1432.         (do-nothing (lambda (val object) (values))))
  1433.  
  1434.     (list
  1435.      (list #\~ char-fail (priv:make-char-id-reader #\~) do-nothing)
  1436.      (list #\a char-alphabetic? locale-reader-abbr-weekday do-nothing)
  1437.      (list #\A char-alphabetic? locale-reader-long-weekday do-nothing)
  1438.      (list #\b char-alphabetic? locale-reader-abbr-month
  1439.            (lambda (val object)
  1440.              (set-date-month! object val)))
  1441.      (list #\B char-alphabetic? locale-reader-long-month
  1442.            (lambda (val object)
  1443.              (set-date-month! object val)))
  1444.      (list #\d char-numeric? ireader2 (lambda (val object)
  1445.                                         (set-date-day!
  1446.                                          object val)))
  1447.      (list #\e char-fail eireader2 (lambda (val object)
  1448.                                      (set-date-day! object val)))
  1449.      (list #\h char-alphabetic? locale-reader-abbr-month
  1450.            (lambda (val object)
  1451.              (set-date-month! object val)))
  1452.      (list #\H char-numeric? ireader2 (lambda (val object)
  1453.                                         (set-date-hour! object val)))
  1454.      (list #\k char-fail eireader2 (lambda (val object)
  1455.                                      (set-date-hour! object val)))
  1456.      (list #\m char-numeric? ireader2 (lambda (val object)
  1457.                                         (set-date-month! object val)))
  1458.      (list #\M char-numeric? ireader2 (lambda (val object)
  1459.                                         (set-date-minute!
  1460.                                          object val)))
  1461.      (list #\S char-numeric? ireader2 (lambda (val object)
  1462.                                         (set-date-second! object val)))
  1463.      (list #\y char-fail eireader2
  1464.            (lambda (val object)
  1465.              (set-date-year! object (priv:natural-year val))))
  1466.      (list #\Y char-numeric? ireader4 (lambda (val object)
  1467.                                         (set-date-year! object val)))
  1468.      (list #\z (lambda (c)
  1469.                  (or (char=? c #\Z)
  1470.                      (char=? c #\z)
  1471.                      (char=? c #\+)
  1472.                      (char=? c #\-)))
  1473.            priv:zone-reader (lambda (val object)
  1474.                               (set-date-zone-offset! object val))))))
  1475.  
  1476. (define (priv:string->date date index format-string str-len port template-string)
  1477.   (define (skip-until port skipper)
  1478.     (let ((ch (peek-char port)))
  1479.       (if (eof-object? port)
  1480.           (priv:time-error 'string->date 'bad-date-format-string template-string)
  1481.           (if (not (skipper ch))
  1482.               (begin (read-char port) (skip-until port skipper))))))
  1483.   (if (>= index str-len)
  1484.       (begin
  1485.         (values))
  1486.       (let ((current-char (string-ref format-string index)))
  1487.         (if (not (char=? current-char #\~))
  1488.             (let ((port-char (read-char port)))
  1489.               (if (or (eof-object? port-char)
  1490.                       (not (char=? current-char port-char)))
  1491.                   (priv:time-error 'string->date
  1492.                                    'bad-date-format-string template-string))
  1493.               (priv:string->date date
  1494.                                  (+ index 1)
  1495.                                  format-string
  1496.                                  str-len
  1497.                                  port
  1498.                                  template-string))
  1499.             ;; otherwise, it's an escape, we hope
  1500.             (if (> (+ index 1) str-len)
  1501.                 (priv:time-error 'string->date
  1502.                                  'bad-date-format-string template-string)
  1503.                 (let* ((format-char (string-ref format-string (+ index 1)))
  1504.                        (format-info (assoc format-char priv:read-directives)))
  1505.                   (if (not format-info)
  1506.                       (priv:time-error 'string->date
  1507.                                        'bad-date-format-string template-string)
  1508.                       (begin
  1509.                         (let ((skipper (cadr format-info))
  1510.                               (reader  (caddr format-info))
  1511.                               (actor   (cadddr format-info)))
  1512.                           (skip-until port skipper)
  1513.                           (let ((val (reader port)))
  1514.                             (if (eof-object? val)
  1515.                                 (priv:time-error 'string->date
  1516.                                                  'bad-date-format-string
  1517.                                                  template-string)
  1518.                                 (actor val date)))
  1519.                           (priv:string->date date
  1520.                                              (+ index 2)
  1521.                                              format-string
  1522.                                              str-len
  1523.                                              port
  1524.                                              template-string))))))))))
  1525.  
  1526. (define (string->date input-string template-string)
  1527.   (define (priv:date-ok? date)
  1528.     (and (date-nanosecond date)
  1529.          (date-second date)
  1530.          (date-minute date)
  1531.          (date-hour date)
  1532.          (date-day date)
  1533.          (date-month date)
  1534.          (date-year date)
  1535.          (date-zone-offset date)))
  1536.   (let ((newdate (make-date 0 0 0 0 #f #f #f #f)))
  1537.     (priv:string->date newdate
  1538.                        0
  1539.                        template-string
  1540.                        (string-length template-string)
  1541.                        (open-input-string input-string)
  1542.                        template-string)
  1543.     (if (not (date-zone-offset newdate))
  1544.     (begin
  1545.       ;; this is necessary to get DST right -- as far as we can
  1546.       ;; get it right (think of the double/missing hour in the
  1547.       ;; night when we are switching between normal time and DST).
  1548.       (set-date-zone-offset! newdate
  1549.                  (priv:local-tz-offset
  1550.                   (make-time time-utc 0 0)))
  1551.       (set-date-zone-offset! newdate
  1552.                  (priv:local-tz-offset
  1553.                   (date->time-utc newdate)))))
  1554.     (if (priv:date-ok? newdate)
  1555.         newdate
  1556.         (priv:time-error
  1557.          'string->date
  1558.          'bad-date-format-string
  1559.          (list "Incomplete date read. " newdate template-string)))))
  1560.  
  1561. ;;; srfi-19.scm ends here
  1562.